package compiler.scanner;

import compiler.scanner.Token.TokenType;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;



public class cMinusScanner implements Scanner{
    private BufferedReader inFile;
    private Token nextToken;
    private String tokenData;
    
    public cMinusScanner(BufferedReader file){
        inFile = file;
        tokenData = "";
        nextToken = scanToken();
    }
    
    @Override
    public Token getNextToken(){
        Token returnToken = nextToken;
        if(nextToken.getType() != Token.TokenType.EOF_TOKEN){
            nextToken = scanToken();
        }
        return returnToken;
    }
    
    @Override
    public Token viewNextToken(){
        return nextToken;
    }

    private Token scanToken() {
        int nextChar;
        int nextState = 0;
        TokenType tokenType = null;
        tokenData = "";
        Token token;
        
        // -1 is the done state
        while (nextState != -1) {
            
            /*
             * get the next character and if not whitespace
             * add it to the current token data
             */
            nextChar = getNextChar();
            if (!Character.isWhitespace(nextChar)) {
                tokenData += (char) nextChar;
            }
            
            /*
             * implementation of DFA
             */
            switch (nextState) {
                case 0: 
                    if (Character.isDigit(nextChar)) {
                        nextState = 1;
                    } else if (Character.isLetter(nextChar)) {
                        nextState = 2;
                    } else if (nextChar == '<') {
                        nextState = 3;
                    } else if (nextChar == '>') {
                        nextState = 4;
                    } else if (nextChar == '=') {
                        nextState = 5;
                    } else if (nextChar == '!') {
                        nextState = 6;
                    } else if (nextChar == '/') {
                        nextState = 7;
                    } else if (nextChar == '+') {
                        tokenType = TokenType.PLUS_TOKEN;
                        nextState = -1;
                    } else if (nextChar == '-') {
                        tokenType = TokenType.MINUS_TOKEN;
                        nextState = -1;
                    } else if (nextChar == '*') {
                        tokenType = TokenType.MULT_TOKEN;
                        nextState = -1;
                    } else if (nextChar == ';') {
                        tokenType = TokenType.SEMI_TOKEN;
                        nextState = -1;
                    } else if (nextChar == ',') {
                        tokenType = TokenType.COMMA_TOKEN;
                        nextState = -1;
                    } else if (nextChar == '(') {
                        tokenType = TokenType.L_PARA_TOKEN;
                        nextState = -1;
                    } else if (nextChar == ')') {
                        tokenType = TokenType.R_PARA_TOKEN;
                        nextState = -1;
                    } else if (nextChar == '[') {
                        tokenType = TokenType.L_BRACKET_TOKEN;
                        nextState = -1;
                    } else if (nextChar == ']') {
                        tokenType = TokenType.R_BRACKET_TOKEN;
                        nextState = -1;
                    } else if (nextChar == '{') {
                        tokenType = TokenType.L_BRACE_TOKEN;
                        nextState = -1;
                    } else if (nextChar == '}') {
                        tokenType = TokenType.R_BRACE_TOKEN;
                        nextState = -1;
                    } else if (nextChar == -1) {
                        tokenType = TokenType.EOF_TOKEN;
                        nextState = -1;
                    } else if (Character.isWhitespace(nextChar)) {
                        nextState = 0;
                    } else {
                        tokenType = TokenType.ERROR_TOKEN;
                        nextState = -1;
                    }
                    break;
                case 1:
                    if (Character.isDigit(nextChar)) {
                        nextState = 1;
                    } else {
                        putBackChar(nextChar);
                        tokenType = TokenType.NUM_TOKEN;
                        nextState = -1;
                    }
                    break;
                case 2:
                    if (Character.isLetter(nextChar)) {
                        nextState = 2;
                    } else {
                        putBackChar(nextChar);
                        tokenType = TokenType.ID_TOKEN;
                        nextState = -1;
                    }
                    break;
                case 3:
                    if (nextChar == '=') {
                        tokenType = TokenType.LESS_EQUAL_TOKEN;
                    } else {
                        putBackChar(nextChar);
                        tokenType = TokenType.LESS_TOKEN;
                    }
                    nextState = -1;
                    break;
                case 4:
                    if (nextChar == '=') {
                        tokenType = TokenType.GREATER_EQUAL_TOKEN;
                    } else {
                        putBackChar(nextChar);
                        tokenType = TokenType.GREATER_TOKEN;
                    }
                    nextState = -1;
                    break;
                case 5:
                    if (nextChar == '=') {
                        tokenType = TokenType.EQUALS_TOKEN;
                    } else {
                        putBackChar(nextChar);
                        tokenType = TokenType.ASSIGNMENT_TOKEN;
                    }
                    nextState = -1;
                    break;
                case 6:
                    if (nextChar == '=') {
                        tokenType = TokenType.NOT_EQUALS_TOKEN;
                    } else {
                        tokenType = TokenType.ERROR_TOKEN;
                    }
                    nextState = -1;
                    break;
                case 7:
                    if (nextChar == '*') {
                        nextState = 8;
                    } else {
                        putBackChar(nextChar);
                        tokenType = TokenType.DIV_TOKEN;
                        nextState = -1;
                    }
                    break;
                case 8:
                    if (nextChar == '*') {
                        nextState = 9;
                    } else {
                        nextState = 8;
                    }
                    break;
                case 9:
                    if (nextChar == '*') {
                        nextState = 9;
                    } else if (nextChar == '/') {
                        nextState = 0;
                        tokenData = "";
                    } else {
                        nextState = 8;
                    }
                    break;
            }
        }
        
        /*
         * once in done state check if identifier is a keyword
         * and handle accordingly otherwise construct the
         * token and return it
         */
        if (tokenType == TokenType.ID_TOKEN) {
            switch (tokenData) {
                case "else":
                    tokenType = TokenType.ELSE_TOKEN;
                    token = new Token(tokenType);
                    break;
                case "if":
                    tokenType = TokenType.IF_TOKEN;
                    token = new Token(tokenType);
                    break;
                case "return":
                    tokenType = TokenType.RETURN_TOKEN;
                    token = new Token(tokenType);
                    break;
                case "void":
                    tokenType = TokenType.VOID_TOKEN;
                    token = new Token(tokenType);
                    break;
                case "while":
                    tokenType = TokenType.WHILE_TOKEN;
                    token = new Token(tokenType);
                    break;
                case "int":
                    tokenType = TokenType.INT_TOKEN;
                    token = new Token(tokenType);
                    break;
                default:
                    token = new Token(tokenType, tokenData);
                    break;    
            } 
        } else if (tokenType == TokenType.NUM_TOKEN) {
            token = new Token(tokenType, tokenData);
        } else {
            token = new Token(tokenType);
        }
        
        return token;
    }    
    
    /*
     * returns the next character as an int, eof does not cast
     * correctly to a char
     */
    private int getNextChar () {
        int c = -1;
        
        try {
            inFile.mark(1);
            c = inFile.read();
        } catch (IOException ex) {
            Logger.getLogger(cMinusScanner.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return c;
    }
    
    /*
     * Moves the reader back one character, in getnextchar
     * the file is marked with each read and this method
     * resets the file to that point. It also removes that
     * character from the current token data.
     */
    private void putBackChar (int nextChar) {
        try {
            inFile.reset();
        } catch (IOException ex) {
            Logger.getLogger(cMinusScanner.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        if (!Character.isWhitespace(nextChar)) {
            tokenData = tokenData.substring(0, tokenData.length() - 1);
        }
    }
}